/** interrupt.c: IRQ-related functions
 *
 * Author: 	Nora Tarano <ntarano@andrew.cmu.edu>
 *          Yuan Meng <yuanm@andrew.cmu.edu>
 *          Yipeng Yun <yipengy@andrew.cmu.edu>
 * Date: 	Dec 11, 2011   
 */

#include <types.h>
#include <exports.h>
#include <assert.h>
#include <math.h>

#include <arm/timer.h>
#include <arm/interrupt.h>
#include <arm/reg.h>

#include <sched.h>

#include "arm/handler.h"

#define IRQ_VECTOR_ADDR (unsigned int*)0x18

unsigned int old_irq_instructions[2];
typedef void (*handler_array)(unsigned int int_num);
handler_array interrupt_handlers[NUM_INTERRUPTS];
extern int irq_wrapper();
unsigned int resched_req;

void interrupt_panic(unsigned int int_num) 
{
	panic("Unknown interrupt received: %d\n", int_num);
}

void init_interrupt(void)
{
	// init handlers to panic
	int i;
	for (i = 0; i < NUM_INTERRUPTS; i++)
		interrupt_handlers[i] = interrupt_panic;
	
	// mask interrupts
	reg_write(INT_ICMR_ADDR, 0);
	// init to not required
	resched_req = 0;
	
	// store and replace U-boot's IRQ handler
	install_handler((unsigned int) irq_wrapper, IRQ_VECTOR_ADDR, old_irq_instructions);
}

void destroy_interrupt(void)
{
	// mask interrupts
	reg_write(INT_ICMR_ADDR, 0);
	
	// restore U-boot's old irq handler
	uninstall_handler(IRQ_VECTOR_ADDR, old_irq_instructions);
}

void irq_handler(void) 
{
	unsigned int pending;
	unsigned int int_num;
	
	// handle all enabled interrupts
	while((pending = reg_read(INT_ICPR_ADDR) & reg_read(INT_ICMR_ADDR)))
	{
		int_num = ilog2(pending);
		interrupt_handlers[int_num](int_num);
	}
	
	// reschedule if necessary
	if (resched_req)
	{
		resched_req = 0;
		dispatch_save();
	}
}

void request_reschedule(void)
{
	resched_req = 1;
}

// install handler for a given interrupt
void install_int_handler(unsigned int int_num, void (*int_handler)(unsigned int))
{
	// create ostmr mask
	int ostmr_mask = 1 << int_num;
	
	// enable interrupt from osmr0
	reg_set(INT_ICMR_ADDR, ostmr_mask);
	
	// set osmr0 to cause irqs
	reg_clear(INT_ICLR_ADDR, ostmr_mask);
	
	interrupt_handlers[int_num]= int_handler;
}
